<!doctype html>
<html lang="en-US">
  <head>
    <link href="/assets/index.css" rel="stylesheet" type="text/css" />
    <script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script>
    <script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.development.js"></script>
    <script crossorigin="anonymous" src="/test-harness.js"></script>
    <script crossorigin="anonymous" src="/test-page-object.js"></script>
    <script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
  </head>
  <body>
    <main id="webchat"></main>
    <script type="importmap">
      {
        "imports": {
          "@testduet/wait-for": "https://unpkg.com/@testduet/wait-for@main/dist/wait-for.mjs",
          "jest-mock": "https://esm.sh/jest-mock",
          "react-dictate-button/internal": "https://unpkg.com/react-dictate-button@main/dist/react-dictate-button.internal.mjs"
        }
      }
    </script>
    <script type="module">
      import { waitFor } from '@testduet/wait-for';
      import { fn, spyOn } from 'jest-mock';
      import {
        SpeechGrammarList,
        SpeechRecognition,
        SpeechRecognitionAlternative,
        SpeechRecognitionErrorEvent,
        SpeechRecognitionEvent,
        SpeechRecognitionResult,
        SpeechRecognitionResultList
      } from 'react-dictate-button/internal';
      import { SpeechSynthesis, SpeechSynthesisEvent, SpeechSynthesisUtterance } from '../speech/js/index.js';
      import renderHook from './private/renderHook.js';

      const {
        React: { createElement },
        ReactDOM: { render },
        testHelpers: { createDirectLineEmulator },
        WebChat: {
          Components: { BasicWebChat, Composer },
          hooks: { useDictateState },
          renderWebChat,
          testIds
        }
      } = window;

      run(async function () {
        const speechSynthesis = new SpeechSynthesis();
        const ponyfill = {
          SpeechGrammarList,
          SpeechRecognition: fn().mockImplementation(() => {
            const speechRecognition = new SpeechRecognition();

            spyOn(speechRecognition, 'abort');
            spyOn(speechRecognition, 'start');

            return speechRecognition;
          }),
          speechSynthesis,
          SpeechSynthesisUtterance
        };

        spyOn(speechSynthesis, 'speak');

        const { directLine, store } = createDirectLineEmulator();
        const WebChatWrapper = ({ children }) =>
          createElement(
            Composer,
            { directLine, store, webSpeechPonyfillFactory: () => ponyfill },
            createElement(BasicWebChat),
            children
          );

        // WHEN: Render initially.
        const renderResult = renderHook(() => useDictateState()[0], {
          legacyRoot: true,
          wrapper: WebChatWrapper
        });

        await pageConditions.uiConnected();

        // THEN: `useDictateState` should returns IDLE.
        await waitFor(() => expect(renderResult).toHaveProperty('result.current', 0)); // IDLE

        // WHEN: Microphone button is clicked and priming user gesture is done.
        await pageObjects.clickMicrophoneButton();

        await waitFor(() => expect(speechSynthesis.speak).toHaveBeenCalledTimes(1));
        speechSynthesis.speak.mock.calls[0][0].dispatchEvent(
          new SpeechSynthesisEvent('end', { utterance: speechSynthesis.speak.mock.calls[0] })
        );

        // THEN: `useDictateState` should returns STARTING.
        renderResult.rerender();
        // Dictate state "1" is for "automatic turning on microphone after current synthesis completed".
        await waitFor(() => expect(renderResult).toHaveProperty('result.current', 2));

        // THEN: Should construct SpeechRecognition().
        expect(ponyfill.SpeechRecognition).toHaveBeenCalledTimes(1);

        const { value: speechRecognition1 } = ponyfill.SpeechRecognition.mock.results[0];

        // THEN: Should call SpeechRecognition.start().
        expect(speechRecognition1.start).toHaveBeenCalledTimes(1);

        // WHEN: Recognition started and interims result is dispatched.
        speechRecognition1.dispatchEvent(new Event('start'));
        speechRecognition1.dispatchEvent(new Event('audiostart'));
        speechRecognition1.dispatchEvent(new Event('soundstart'));
        speechRecognition1.dispatchEvent(new Event('speechstart'));

        // WHEN: Recognized interim result of "Hello".
        speechRecognition1.dispatchEvent(
          new SpeechRecognitionEvent('result', {
            results: new SpeechRecognitionResultList(
              new SpeechRecognitionResult(new SpeechRecognitionAlternative(0, 'Hello'))
            )
          })
        );

        // THEN: `useDictateState` should returns DICTATING.
        renderResult.rerender();
        await waitFor(() => expect(renderResult).toHaveProperty('result.current', 3));

        // WHEN: Recognized finalized result of "Hello, World!" and ended recognition.
        await (
          await directLine.actPostActivity(() =>
            speechRecognition1.dispatchEvent(
              new SpeechRecognitionEvent('result', {
                results: new SpeechRecognitionResultList(
                  SpeechRecognitionResult.fromFinalized(new SpeechRecognitionAlternative(0.9, 'Hello, World!'))
                )
              })
            )
          )
        ).resolveAll();

        speechRecognition1.dispatchEvent(new Event('speechend'));
        speechRecognition1.dispatchEvent(new Event('soundend'));
        speechRecognition1.dispatchEvent(new Event('audioend'));
        speechRecognition1.dispatchEvent(new Event('end'));

        // THEN: `useDictateState` should returns IDLE.
        renderResult.rerender();
        await waitFor(() => expect(renderResult).toHaveProperty('result.current', 0));

        // WHEN: Bot replied.
        await directLine.emulateIncomingActivity({
          inputHint: 'expectingInput', // "expectingInput" should turn the microphone back on after synthesis completed.
          text: 'Aloha!',
          type: 'message'
        });
        await pageConditions.numActivitiesShown(2);

        // THEN: Should call SpeechSynthesis.speak() again.
        await waitFor(() => expect(speechSynthesis.speak).toHaveBeenCalledTimes(2));

        // THEN: Should start synthesize "Aloha!".
        expect(speechSynthesis.speak).toHaveBeenLastCalledWith(expect.any(SpeechSynthesisUtterance));
        expect(speechSynthesis.speak).toHaveBeenLastCalledWith(expect.objectContaining({ text: 'Aloha!' }));

        // THEN: `useDictateState` should returns WILL_START.
        renderResult.rerender();
        await waitFor(() => expect(renderResult).toHaveProperty('result.current', 1));

        // WHEN: Synthesis completed.
        speechSynthesis.speak.mock.calls[1][0].dispatchEvent(
          new SpeechSynthesisEvent('end', { utterance: speechSynthesis.speak.mock.calls[1] })
        );

        // THEN: `useDictateState` should returns STARTING.
        renderResult.rerender();
        await waitFor(() => expect(renderResult).toHaveProperty('result.current', 2));

        // WHEN: Recognition started and interims result is dispatched.
        const { value: speechRecognition2 } = ponyfill.SpeechRecognition.mock.results[1];

        // THEN: Should call SpeechRecognition.start().
        expect(speechRecognition2.start).toHaveBeenCalledTimes(1);

        // WHEN: Recognition started and interims result is dispatched.
        speechRecognition2.dispatchEvent(new Event('start'));
        speechRecognition2.dispatchEvent(new Event('audiostart'));
        speechRecognition2.dispatchEvent(new Event('soundstart'));
        speechRecognition2.dispatchEvent(new Event('speechstart'));

        // WHEN: Recognized interim result of "Good".
        speechRecognition2.dispatchEvent(
          new SpeechRecognitionEvent('result', {
            results: new SpeechRecognitionResultList(
              new SpeechRecognitionResult(new SpeechRecognitionAlternative(0, 'Good'))
            )
          })
        );

        // THEN: `useDictateState` should returns LISTENING.
        renderResult.rerender();
        await waitFor(() => expect(renderResult).toHaveProperty('result.current', 3));

        // WHEN: Click on microphone button.
        await pageObjects.clickMicrophoneButton();

        // THEN: `useDictateState` should returns STOPPING.
        renderResult.rerender();
        await waitFor(() => expect(renderResult).toHaveProperty('result.current', 4));

        // WHEN: Recognition ended.
        speechRecognition2.dispatchEvent(new Event('speechend'));
        speechRecognition2.dispatchEvent(new Event('soundend'));
        speechRecognition2.dispatchEvent(new Event('audioend'));
        speechRecognition2.dispatchEvent(new Event('end'));

        // THEN: `useDictateState` should returns STOPPING.
        renderResult.rerender();
        await waitFor(() => expect(renderResult).toHaveProperty('result.current', 0));
      });
    </script>
  </body>
</html>
